#!/usr/bin/python
# coding=utf-8

import select
import socket
import os,sys,subprocess,signal
import re
import logging
from threading import Thread 

host = "localhost"
port = 9988
PF_CYGWIN = sys.platform == "cygwin"
PF_WIN32 = sys.platform == "win32"

def before_exit(func):
    def handler(signal_num,frame):
        print "\nYou Pressed Ctrl-C."
        func()
        sys.exit(signal_num)
    signal.signal(signal.SIGINT, handler)

class Command():
    def __init__(self, addr, cmd, child):
        self.addr = addr
        self.cmd = cmd
        self.child = child
    def write(self,msg):
        self.child.stdin.write(msg)
        self.child.stdin.flush()
    def __str__(self):
        return 'Command(pid=%s, from="%s:%s", cmd=[%s])' % (self.child.pid,self.addr[0],self.addr[1],self.cmd)

class Server():
    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    cmd_addr = {}
    addr_cmd = {}
    addr_pid = {}
    def __init__(self,host,port):
        self.sock.bind(('',port))
        self.inputs = [self.sock]
        if not PF_WIN32:
            self.inputs.append(sys.stdin)
    def serv(self):
        cmds = {}
        before_exit(self.clear)
        while True:
            rlist, wlist, _ = select.select(self.inputs, [], [])
            for r in rlist:
                if r==self.sock:
                    sock_msg = ""
                    try:
                        sock_msg,addr = r.recvfrom(1024)
                        command = self.addr_cmd.get(addr)
                        if command:
                            # 已经运行
                            if not sock_msg or sock_msg.endswith(",,"):
                                self.kill(addr)
                            else:
                                command.write(sock_msg)
                        else:
                            # 执行命令
                            cmd = cmds.get(addr,"") + sock_msg
                            cmds[addr] = cmd
                            if cmd.endswith(",,"):
                                cmd = cmd[:-2]
                                self.run(addr, cmd)
                                del cmds[addr]
                    except Exception:
                        pass
                elif r==sys.stdin:
                    cmd = r.readline()
                    if cmd.startswith("show"):
                        self.show()
                    elif cmd.startswith("clear"):
                        self.clear()
                else:
                    cmd_msg = r.readline()
                    addr = self.cmd_addr[r]
                    if not cmd_msg:
                        # 程序退出
                        self.inputs.remove(r)
                        del self.cmd_addr[r]
                        if addr in self.addr_cmd:
                            command = self.addr_cmd[addr]
                            logging.info("exit %s", command)
                            del self.addr_cmd[addr]
                            self.sock.sendto(",,",addr)
                    else:
                        # 程序输出
                        self.sock.sendto(cmd_msg,addr)
    def read_loop(self,r,command,flag=False):
        while True:
            cmd_msg = r.readline()
            if not cmd_msg:
                # 程序退出
                if flag and command.addr in self.addr_cmd:
                    logging.info("exit %s", command)
                    del self.addr_cmd[command.addr]
                self.sock.sendto(",,",command.addr)
                return
            else:
                # 程序输出
                self.sock.sendto(cmd_msg,command.addr)

    def run(self,addr,cmd):
        child = subprocess.Popen(cmd, stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)  
        command = Command(addr, cmd, child)
        logging.info("run  %s", command)
        if PF_WIN32:
            Thread(target=self.read_loop, args=(child.stdout,command,True)).start()
            Thread(target=self.read_loop, args=(child.stderr,command)).start()
        else:
            self.inputs.append(child.stdout)
            self.inputs.append(child.stderr)
            self.cmd_addr[child.stdout] = addr
            self.cmd_addr[child.stderr] = addr
        self.addr_cmd[addr] = command

    def kill(self,addr):
        command = self.addr_cmd[addr]
        logging.info("kill %s", command)
        command.child.kill()
        
    def show(self):
        print "commands:"
        for command in self.addr_cmd.values():
            print command
        print len(self.cmd_addr),len(self.inputs)-2

    def clear(self):
        for addr in self.addr_cmd.keys():
            self.kill(addr)

# 加引号
def add_quotes(func):
    def _add_quotes(s):
        if not re.search(r'"|=',s):
            return '"'+s+'"'
        if not re.search(r"'|=",s):
            return "'"+s+"'"
        return s
    if callable(func):
        def call(row):
            return _add_quotes(func(row))
        return call
    return _add_quotes(func)        

# 将 cygwin 路径转换为 windows 路径
def cygwin_path(path):
    if PF_CYGWIN:
        paths = path.split("/")
        if len(paths)>2 and paths[1]=="cygdrive":
            path = paths[2].upper()+":\\" + "\\".join(paths[3:])
        else:
            path = "C:\cygwin64\\" + "\\".join(paths[1:])
        return path.encode("gbk")
    return path

# 包装一个参数
def warp_path(cmd):
    # 特殊处理
    if len(cmd)>2 and cmd.startswith("-") and cmd.endswith("-"):
        return add_quotes(cmd[1:-1])
    # 如果是 -c=xxx 包装xxx并加上引号
    key = "="
    rows = cmd.split(key)
    if len(rows)>1:
        rows[1] = add_quotes(warp_path(rows[1]))
        return key.join(rows)
    # 如果是 xxx:12 包装xxx并加上引号
    key = ":"
    rows = cmd.split(key)
    if len(rows)>1:
        rows[0] = add_quotes(warp_path(rows[0]))
        return key.join(rows)
    # 如果是文件 转换为windows绝对路径
    path = os.getcwd()
    path = os.path.join(path,cmd)
    path = os.path.realpath(path)
    if os.path.exists(path):
        return cygwin_path(path)
    return cmd

# 包装所有的参数
def warp_cmds(cmds):
    return " ".join(map(warp_path, cmds))

def client(cmd):
    cmd = warp_cmds(cmd)
    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    addr = (host,port)
    sock.sendto(cmd+",,",addr)
    msg = ""
    before_exit(lambda :sock.sendto(",,",addr))
    flag = True
    while flag:
        rlist,_,_ = select.select([sock,sys.stdin], [], [])
        for r in rlist:
            if r==sock:
                s, addr = sock.recvfrom(2048)
                if not s:
                    print "连接中断"
                    sock.close()
                    exit()
                msg = msg[-2:] + s
                if msg.endswith(",,"):
                    flag = False
                    print msg[2:-2],
                else:
                    print s,
            else:
                s = r.readline()
                sock.sendto(s,addr)
    sock.close()

if __name__=="__main__":
    if len(sys.argv)>1:
        client(sys.argv[1:])
    else:
        logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s: %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S',
                        filename='server.log',
                        filemode='w')
        Server(host,port).serv()
